<?php
declare(strict_types=1);

namespace WebApp\lib\db;

use ErrorException;
use MongoDB\Driver\BulkWrite;
use MongoDB\Driver\Command;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Exception\Exception;
use MongoDB\Driver\Manager;
use MongoDB\Driver\Query;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use Throwable;
use WebApp\lib\util\Util;

/**
 * Class Collection
 * @package txz\common\third_lib\model\lib
 */
class Collection extends AbstractTable {
    private $concern = null;
    public $_id;


    /**
     * @param $server_name
     * @param $dbName
     * @param $tableName
     * @param string $pre
     * @param string $suffix
     * @return mixed
     */
    public function process($server_name, $dbName, $tableName, $pre = '', $suffix = ''):void
    {
        $this->setPre($pre);
        $this->setSuffix($suffix);
        $this->setDbName($dbName);
        $this->setServerName($server_name);
        $this->setTableName(($pre ? $pre . '_' : '') . $tableName . ($suffix ? '_' . $suffix : ''));
        $this->setConcern(new WriteConcern(WriteConcern::MAJORITY, 1000));
    }

    /**
     * @param $id
     * @param array $arr
     * @return bool
     */
    public function insertById($id, array $arr)
    {
        try {
            if (!$id)
                throw new ErrorException('no id');

            $arr["_id"] = $id;

            return $this->insert($arr);
        } catch (ErrorException $e) {
            tracker(E, $e->getMessage());
            return false;
        }
    }

    /**
     * @param $arr
     * @return array|int
     */
    public function insert(array $arr)
    {
        $bulk = new BulkWrite;
        $bulk->insert($arr);
        return $this->execute($bulk);
    }

    /**
     * @param $need
     * @param bool $id_flag
     * @return array
     */
    public static function arr2need($need, $id_flag = false)
    {
        $option = [];
        if ($need) {
            foreach ($need as $val)
                $option[$val] = 1;
        } else {
            $option['y'] = 1;
        }

        if (!$id_flag) {
            $option["_id"] = 0;
        }
        $options = [
            'projection' => $option
        ];
        return $options;
    }

    public function object_to_array($obj) {
        $obj = (array)$obj;
        foreach ($obj as $k => $v) {
            if (gettype($v) == 'resource') {
                return;
            }
            if (gettype($v) == 'object' || gettype($v) == 'array') {
                $obj[$k] = (array)$this->object_to_array($v);
            }
        }

        return $obj;
    }

    /**
     * @param array $filter
     * @param array $options
     * @param null $limit
     * @param null $offset
     * @param bool $is_array
     * @return array|bool
     */
    public function findAll($filter = [], $options = [], $limit = null, $offset = null, $is_array = false)
    {
        if ($limit && is_numeric($limit)) {
            $options['limit'] = $limit;
        }

        if ($offset && is_numeric($offset)) {
            $options['skip'] = $offset;
        }

        $query = new Query($filter, $options);
        /** @var Cursor $cursor */
        $cursor = MongoPool::getInstance()->exec(function(Manager $client) use($query){
            return $client->executeQuery($this->getDbName() . "." . $this->getTableName(), $query);
        });
        if ($cursor) {
            if ($is_array) {
                return $this->object_to_array($cursor->toArray());
            } else {
                return $cursor->toArray();
            }
        }
        return false;
    }

    private function executeCommand($command, $is_array){
        $cursor = MongoPool::getInstance()->exec(function(Manager $client) use($command){
            return $client->executeCommand($this->getDbName(), new Command($command));
        });
        if ($cursor) {
            if ($is_array) {
                return $this->object_to_array($cursor->toArray());
            } else {
                return $cursor->toArray();
            }
        }
        return false;
    }

    public function aggregate(array $pipeline, array $options = [], bool $is_array = false): array
    {
        $command = [
                'aggregate' => $this->getTableName(),
                'pipeline'  => $pipeline
            ] + $options;
        return $this->executeCommand($command, $is_array);
    }


    /**
     * @param array $filter
     * @param array $options
     * @return object|bool
     */
    public function find($filter = [], $options = [])
    {
        $res = $this->findAll($filter, $options);
        if (is_array($res) && $res) {
            return $res[0];
        } else {
            return $res;
        }
    }


    /**
     * @param $filter
     * @param int $limit
     * @return array|int
     */
    public function remove($filter, $limit = 1)
    {
        $bulk = new BulkWrite;
        if ($limit <= 0) {
            $bulk->delete($filter);
        } else {
            $bulk->delete($filter, ['limit' => $limit]);
        }
        return $this->execute($bulk);
    }

    /**
     * @param $bulk
     * @return bool
     */
    private function execute($bulk): bool
    {
        try{
            $res = MongoPool::getInstance()->exec(function(Manager $client) use($bulk){
                return $client->executeBulkWrite($this->getDbName() . "." . $this->getTableName(), $bulk, $this->getConcern());
            });
            if($res->getWriteConcernError()){
                $this->handle_db_error($res->getWriteConcernError());
                return false;
            }else{
                if ($res->getWriteErrors()){
                    throw new ErrorException($res->getWriteErrors());
                }else{
                    return true;
                }
            }
        }catch (ErrorException $e){
            tracker(E, $e->getMessage());
            return false;
        }
    }
    /**
     * @param array $filter
     * @param $arr
     * @param array $multi_upset
     * @param string $opt
     * @return array|int
     */
    public function update($filter = [], $arr, $multi_upset = [false, false], $opt = '$set')
    {
        $bulk = new BulkWrite;
        $bulk->update(
            $filter,
            [$opt => $arr],
            ['multi' => $multi_upset[0], 'upsert' => $multi_upset[1]]
        );
        return $this->execute($bulk);
    }


    /**
     * @param $e
     */
    private function handle_db_error($e)
    {
        var_dump($e);
    }

    /**
     * @return null
     */
    public function getConcern()
    {
        return $this->concern;
    }

    /**
     * @param null $concern
     */
    public function setConcern($concern)
    {
        $this->concern = $concern;
    }

    /**
     * @return mixed
     */
    public function getPool()
    {
        // TODO: Implement getPool() method.
    }
}